package demo.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import demo.annotation.MethodCache;
import demo.vo.common.CachedObject;
import demo.service.CacheService;

/**
 * 方法级缓存拦截器 注意！！！方法入参必须为基本数据类型或者字符串类型，使用其它引用类型的参数会导致缓存键构造有误
 * 
 * @author hanjy
 */

@Aspect
public class MethodCacheAspect {

    private static Logger logger = LoggerFactory.getLogger(MethodCacheAspect.class);

    @Autowired
    private CacheService cacheService;

    /**
     * 搭配 AspectJ 指示器“@annotation()”可以使本切面成为某个注解的代理实现
     */
    @Around("@annotation(demo.annotation.MethodCache)")
    private Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodCache methodCache = getAnnotation(joinPoint, MethodCache.class);

        long expire = methodCache.expire();
        String keyPattern = methodCache.keyPattern();

        String cacheKey = buildCacheKey(joinPoint, keyPattern);

        CachedObject object = cacheService.get(cacheKey);

        if (object != null) {
            logger.info("cache hit，key [{}]", cacheKey);
            return object.getData();
        }

        logger.info("cache miss，key [{}]", cacheKey);
        Object result = joinPoint.proceed(joinPoint.getArgs());
        cacheService.set(cacheKey, new CachedObject(result), expire);
        return result;
    }

    private static final Pattern pattern = Pattern.compile("\\{\\d+\\}");

    /**
     * 根据类名、方法名和参数值获取唯一的缓存键
     * 
     * @return 格式为 "包名.类名.方法名.参数类型.参数值"，类似
     *         "your.package.SomeService.getById(int).123"
     */
    private static String buildCacheKey(ProceedingJoinPoint joinPoint, String keyPattern) {
        Object[] args = joinPoint.getArgs();
        if (keyPattern == null || keyPattern.isEmpty()) {
            String argStr = (args == null || args.length == 0) ? "null"
                    : Stream.of(args).map(String::valueOf).collect(Collectors.joining(","));
            return String.format("%s.%s", joinPoint.getSignature().toString().split("\\s")[1], argStr);
        }
        Matcher matcher = pattern.matcher(keyPattern);
        String key = keyPattern;
        while (matcher.find()) {
            String group = matcher.group();
            int index = Integer.parseInt(group.substring(1, group.length() - 1));
            key = key.replace(group, String.valueOf(args[index]));
        }
        return key;
    }

    private static <T extends Annotation> T getAnnotation(ProceedingJoinPoint joinPoint, Class<T> clazz) {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();
        return method.getAnnotation(clazz);
    }
}
